Imports System.Windows.Forms

Public Class frmLowFlow

    Dim DataTable As clsDataTable
    Private dgvDaily, dgvMissing, dgvDeleted, dgvAnnual, dgvPlotting As WRDB.Controls.DGVEditor
    Dim lstDataRecord As Generic.List(Of clsDataRecord)
    Dim lstMovingAvg As Generic.List(Of clsDataRecord)
    Dim lstAnnualSeries As Generic.List(Of clsDataRecord)
    Dim TableName As String

    Friend Function CalcLowFlow() As Boolean
        Try
            TabMain.Enabled = False
            dgDaily.Rows.Clear()
            dgMissing.Rows.Clear()
            dgDeleted.Rows.Clear()
            dgAnnual.Rows.Clear()
            dgPlotting.Rows.Clear()
            Application.UseWaitCursor = True
            Application.DoEvents()
            DataTable = New clsDataTable(Project, clsProject.TempData)
            Dim CCode As String = Project.DB.GetPickList(cboCCodeIn)
            If CCode = "<NULL>" Then CCode = "IS NULL" Else CCode = "= '" & CCode & "'"
            If CCode.StartsWith("(Create") Then
                WarningMsg("You must select a valid CCode before continuing.")
                Return False
            End If

            Dim StationIDs As Generic.List(Of String) = DataTable.SelectDistinctRecords("Station_ID", String.Format("PCode='{0}' AND CCode {1} AND Date_Time BETWEEN {2} AND {3}", Project.DB.GetPickList(cboPCodeIn), CCode, Project.DB.DateTime(txtMinDate.Text), Project.DB.DateTime(txtMaxDate.Text)))
            If StationIDs Is Nothing OrElse StationIDs.Count = 0 Then
                WarningMsg("There are no selected records having the PCode and CCode within the date range that you specified.") : Return False
            ElseIf StationIDs.Count > 1 Then
                WarningMsg("There was more than one Station ID matching the PCode and CCode within the date range that you specified.") : Return False
            End If
            Dim StationID As String = StationIDs(0)

            lstDataRecord = DataTable.SelectRecords(String.Format("PCode='{0}' AND CCode {1} AND Date_Time BETWEEN {2} AND {3}", Project.DB.GetPickList(cboPCodeIn), CCode, Project.DB.DateTime(txtMinDate.Text), Project.DB.DateTime(txtMaxDate.Text)), "Date_Time")

            DataTable.Close()

            TableName = cboTableName.Text
            If cboTableName.SelectedIndex = 0 Then TableName = ActiveBrowser.FullTableName

            If Not MainForm.CheckDataTableStructure(TableName, True) Then Return False

            DataTable = New clsDataTable(Project, TableName)

            Dim PCode As String = Project.DB.GetPickList(cboPCodeOut)
            CCode = Project.DB.GetPickList(cboCCodeOut)

            Dim NumFound As Integer = DataTable.GetCount("PCode='{0}' AND CCode='{1}'", PCode, CCode)

            DataTable.BeginTransaction()

            If NumFound > 0 Then
                Select Case MessageBox.Show(StringFormat("Before computing all values, do you want to delete all {0} existing records in this Working table that have the specified PCode ({1}) and CCode ({2})?", NumFound, PCode.Trim, CCode.Trim), "Calculate Low Flow", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question)
                    Case Windows.Forms.DialogResult.Yes
                        DataTable.DeleteRecords("PCode='{0}' AND CCode='{1}'", PCode, CCode)
                    Case Windows.Forms.DialogResult.Cancel
                        Exit Function
                End Select
            End If

            Try
                Dim FirstMonth As Integer = cboFirstMonth.SelectedIndex + 1
                Dim LastMonth As Integer = cboLastMonth.SelectedIndex + 1
                Dim NumMissing As Integer = 0
                Dim NumDeleted As Integer = 0
                Dim Warnings As String = ""

                MainForm.WriteProgress("Scanning for missing data...")

                dgvMissing.SetHeadings("Date~" & IIf(cboMissingValues.SelectedIndex = 0, "Inserted|Value", "No. Missing|Days"), , 0)

                Dim RecNum As Integer = 1
                If lstDataRecord.Count <= 1 Then Return False

                Do
                    Dim thisdate As Date = lstDataRecord(RecNum).DateTime
                    Dim lastdate As Date = lstDataRecord(RecNum - 1).DateTime
                    Dim thisresult As Double = lstDataRecord(RecNum).Result
                    Dim lastresult As Double = lstDataRecord(RecNum - 1).Result
                    Dim avgresult As Double

                    Dim numHours As Double = thisdate.Subtract(lastdate).TotalHours
                    If numHours < 23.999 Then
                        dgvDeleted.AddRowItems(thisdate, thisresult)
                        lstDataRecord.RemoveAt(RecNum)
                        NumDeleted += 1
                        Continue Do
                    ElseIf numHours > 24.001 Then
                        NumMissing += 1
                        If cboMissingValues.SelectedIndex = 0 Then 'want to fill all missing values with averages
                            If avgresult = 0 Then avgresult = (lastresult + thisresult) / 2
                            thisdate = lastdate.AddDays(1)
                            thisresult = avgresult
                            dgvMissing.AddRowItems(thisdate, thisresult)
                            With lstDataRecord(RecNum - 1)
                                lstDataRecord.Insert(RecNum, New clsDataRecord(.StationID, thisdate, .PCode, thisresult, .CCode))
                            End With
                        Else 'want to display number of missing values
                            dgvMissing.AddRowItems(thisdate, thisdate.Subtract(lastdate).TotalDays - 1)
                        End If
                    Else
                        avgresult = 0 'flag that am not inserting missing values
                    End If
                    If Not MainForm.WriteProgress(RecNum, lstDataRecord.Count - 1, "Scanning for missing data...", ProgressBarStyle.Blocks, True) Then Return False
                    RecNum += 1
                Loop Until RecNum > lstDataRecord.Count - 1


                MainForm.WriteProgress("Computing moving averages...")

                lstMovingAvg = New Generic.List(Of clsDataRecord)

                Dim Period As Integer = cboNumDays.Text
                For i As Integer = 0 To lstDataRecord.Count - 1
                    Dim sum As Double = 0
                    Dim Count As Integer = 0
                    Dim jmin As Integer = i - Period \ 2
                    For j As Integer = Math.Max(0, jmin) To Math.Min(lstDataRecord.Count - 1, jmin + Period - 1)
                        sum += lstDataRecord(j).Result
                        Count += 1
                    Next j
                    With lstDataRecord(i)
                        Dim ma As New clsDataRecord(.StationID, .DateTime, .PCode, sum / Count)
                        lstMovingAvg.Add(ma)
                    End With
                    If Not MainForm.WriteProgress(i, lstDataRecord.Count - 1, "Computing moving averages...", ProgressBarStyle.Blocks, True) Then Return False
                Next i

                MainForm.WriteProgress("Computing annual series...")

                'skip past first partial year and truncate last partial year
                Dim startIndex As Integer = -1
                For i As Integer = 0 To lstMovingAvg.Count - 1
                    Dim thisdate As Date = lstMovingAvg(i).DateTime
                    If thisdate.Month = FirstMonth And thisdate.Day = 1 Then startIndex = i : Exit For
                Next

                Dim endIndex As Integer = -1
                For i As Integer = lstMovingAvg.Count - 1 To Max(startIndex, 0) Step -1
                    Dim thisdate As Date = lstMovingAvg(i).DateTime
                    If thisdate.Month = LastMonth And thisdate.Day = Date.DaysInMonth(thisdate.Year, thisdate.Month) Then endIndex = i : Exit For
                Next

                If startIndex = -1 Or endIndex = -1 Then
                    WarningMsg("Less than one year of data is available, unable to perform low flow analysis.")
                    Return False
                End If

                If startIndex > 0 Then
                    Select Case MessageBox.Show(String.Format("The first record available in the first year of data is {0:MM/dd/yyyy}; do you want to include this year in the annual series even though some data are missing?", lstDataRecord(0).DateTime), "Warning", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button1)
                        Case Windows.Forms.DialogResult.Yes : startIndex = 0
                        Case Windows.Forms.DialogResult.No
                        Case Windows.Forms.DialogResult.Cancel : Return False
                    End Select
                End If

                If endIndex < lstDataRecord.Count - 1 Then
                    Select Case MessageBox.Show(String.Format("The last record available in the last year of data is {0:MM/dd/yyyy}; do you want to include this year in the annual series even though some data are missing?", lstDataRecord(lstDataRecord.Count - 1).DateTime), "Warning", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button1)
                        Case Windows.Forms.DialogResult.Yes : endIndex = lstDataRecord.Count - 1
                        Case Windows.Forms.DialogResult.No
                        Case Windows.Forms.DialogResult.Cancel : Return False
                    End Select
                End If

                Dim firstYear As Integer = CDate(lstMovingAvg(startIndex).DateTime).Year
                If FirstMonth > LastMonth Then firstYear += 1
                Dim lastYear As Integer = CDate(lstMovingAvg(endIndex).DateTime).Year

                lstAnnualSeries = New Generic.List(Of clsDataRecord)

                Dim prevYear As Integer = firstYear
                Dim minmaxIndex As Integer
                Dim minmaxResult As Double = IIf(cboExtractType.SelectedIndex = 0, Double.MaxValue, Double.MinValue)
                Static AlreadyWarned As Boolean = False

                For i As Integer = startIndex To endIndex
                    Dim thisDate As Date = lstMovingAvg(i).DateTime
                    Dim thisResult As Double = lstMovingAvg(i).Result
                    If thisDate.Year + IIf(FirstMonth > LastMonth And thisDate.Month >= FirstMonth, 1, 0) > prevYear Or i = endIndex Then 'save prior year results
                        With lstMovingAvg(minmaxIndex)
                            If .Result = 0 And Not AlreadyWarned Then
                                .Result = 0.005
                                WarningMsg("One or more years in the annual series had values of 0.00; for the purposes of the low flow analysis and graphing these have been changed to 0.005. The user should evaluate these data and consider a more advanced frequency analysis which better accounts for zero flows.")
                                AlreadyWarned = True
                            End If
                            .Result = Math.Round(.Result, 2)
                            .PCode = PCode
                            .CCode = CCode
                            lstAnnualSeries.Add(lstMovingAvg(minmaxIndex))
                            dgvAnnual.AddRowItems(.DateTime, .Result)
                            minmaxResult = IIf(cboExtractType.SelectedIndex = 0, Double.MaxValue, Double.MinValue)
                            prevYear = thisDate.Year + IIf(FirstMonth > LastMonth And thisDate.Month >= FirstMonth, 1, 0)
                        End With
                    End If
                    If IIf(cboExtractType.SelectedIndex = 0, thisResult < minmaxResult, thisResult > minmaxResult) And IIf(FirstMonth <= LastMonth, thisDate.Month >= FirstMonth And thisDate.Month <= LastMonth, thisDate.Month >= FirstMonth Or thisDate.Month <= LastMonth) Then
                        minmaxResult = thisResult
                        minmaxIndex = i
                    End If
                    If Not MainForm.WriteProgress(i, lstMovingAvg.Count - 1, "Computing annual series...", ProgressBarStyle.Blocks, True) Then Return False
                Next i

                'compute plotted values
                Dim ar(lstAnnualSeries.Count - 1) As Double
                For i As Integer = 0 To ar.Length - 1
                    ar(i) = lstAnnualSeries(i).Result
                Next
                Array.Sort(ar)
                For i As Integer = 0 To ar.Length - 1
                    dgvPlotting.AddRowItems((i + 1) / (ar.Length + 1), ar(i))
                Next

                'write to working table

                For i As Integer = 0 To lstAnnualSeries.Count - 1
                    If Not DataTable.InsertRecord(lstAnnualSeries(i)) Then WarningMsg("Unable to insert computed value.") : Return False
                Next

                MainForm.WriteProgress("Saving results...")

                dgDaily.RowCount = lstDataRecord.Count

                MainForm.ProgressComplete()

                If NumMissing + NumDeleted > 0 Then MessageBox.Show(StringFormat("{0} {3}missing values were {1}; {2} values had non non-daily time step (and were deleted); please carefully review the results before proceeding.", NumMissing, IIf(cboMissingValues.SelectedIndex = 0, "inserted as averages", "ignored"), NumDeleted, IIf(cboMissingValues.SelectedIndex = 0, "", "periods of ")), "Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning)

                For i As Integer = 1 To 5
                    With CType(Choose(i, dgvDaily, dgvDeleted, dgvMissing, dgvAnnual, dgvPlotting), WRDB.Controls.DGVEditor)
                        .RenumberRows()
                        .DataGridView.AutoResizeRowHeadersWidth(DataGridViewRowHeadersWidthSizeMode.AutoSizeToAllHeaders)
                    End With
                Next
                Return True

            Catch ex As DbSqlException
                DataTable.Close(clsDataTable.enumEndTransaction.Rollback)
                WarningMsg(ex.Message)
                Return False
            Catch ex As Exception
                DataTable.Close(clsDataTable.enumEndTransaction.Rollback)
                ErrorMsg(, ex)
                Return False
            Finally
                IniFile.Save()
                TabMain.Enabled = True
                'these are required due to bug in .Net causing vertical scrollbars to stay disabled
                dgDaily.PerformLayout()
                dgDeleted.PerformLayout()
                dgMissing.PerformLayout()
                dgAnnual.PerformLayout()
                dgPlotting.PerformLayout()
            End Try
        Catch ex As Exception
            ErrorMsg(, ex)
        Finally
            Application.UseWaitCursor = False
            MainForm.ProgressComplete()
        End Try
    End Function

    Private Sub frmLowFlow_FormClosed(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosedEventArgs) Handles Me.FormClosed
        SaveControlValues(REGAPP, Me)
    End Sub

    Private Sub frmCalc_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        Try
            Project.DB.FillPickList(cboPCodeIn, clsProject.TempData, "PCodes", "PCode", "Anal_Name")
            Project.DB.FillPickList(cboCCodeIn, clsProject.TempData, "CCodes", "CCode", "CCode_Name")
            Project.DB.FillPickList(cboPCodeOut, "", "PCodes", "PCode", "Anal_Name", "(Create New)")
            Project.DB.FillPickList(cboCCodeOut, "", "CCodes", "CCode", "CCode_Name", "(Create New)")
            frmCalc.RefreshTableList(cboTableName)
            GetControlValues(REGAPP, Me)
            pnlRange.Enabled = False
            cboNumDays.SelectedIndex = 2
            cboExtractFor.SelectedIndex = 0
            cboFirstMonth.SelectedIndex = 0
            cboLastMonth.SelectedIndex = 11
            cboExtractType.SelectedIndex = 0
            cboExtractFor.SelectedIndex = 0
            cboMissingValues.SelectedIndex = 0
            If cboPCodeOut.Items.Count = 0 Or cboCCodeOut.Items.Count = 0 Then
                WarningMsg("You cannot use this feature if either the PCode or CCode support tables are empty.")
                btnCalc.Enabled = False
            End If

            dgvDaily = New WRDB.Controls.DGVEditor(dgDaily)
            dgvMissing = New WRDB.Controls.DGVEditor(dgMissing)
            dgvDeleted = New WRDB.Controls.DGVEditor(dgDeleted)
            dgvAnnual = New WRDB.Controls.DGVEditor(dgAnnual)
            dgvPlotting = New WRDB.Controls.DGVEditor(dgPlotting)

            For i As Integer = 1 To 5
                With CType(Choose(i, dgvDaily, dgvDeleted, dgvMissing, dgvAnnual, dgvPlotting), WRDB.Controls.DGVEditor)
                    .SetHeadings(IIf(i = 5, "Plotting|Prob~", "Date~") & Choose(i, "Observed|Value~Moving|Avg", "Deleted|Value", IIf(cboMissingValues.SelectedIndex = 0, "Inserted|Value", "No. Missing|Days"), "Annual|Minimum", "Annual|Minimum"), True, 0, i = 1)
                    For c As Integer = 0 To .DataGridView.Columns.Count - 1
                        .DataGridView.Columns(c).DefaultCellStyle.Format = Choose(c + 1, IIf(i = 5, "N3", MyDateFormat), IIf(i = 3 And cboMissingValues.SelectedIndex = 1, "N0", "N2"), "N2")
                    Next
                    .DataGridView.ReadOnly = True
                End With
            Next

            cboPCodeIn.Text = GetSetting(REGAPP, Name, "cboPCodeIn")
            cboCCodeIn.Text = GetSetting(REGAPP, Name, "cboCCodeIn")
            txtMinDate.Text = CType(Project.DB.ExecuteScalar("SELECT MIN(Date_Time) FROM {0}", clsProject.TempData), Date).ToString(MyDateFormat)
            txtMaxDate.Text = CType(Project.DB.ExecuteScalar("SELECT MAX(Date_Time) FROM {0}", clsProject.TempData), Date).ToString(MyDateFormat)
            cboPCodeOut.Text = GetSetting(REGAPP, Name, "cboPCodeOut")
            cboCCodeOut.Text = GetSetting(REGAPP, Name, "cboCCodeOut")
            cboNumDays.Text = GetSetting(REGAPP, Name, "cboNumDays")
            cboExtractType.Text = GetSetting(REGAPP, Name, "cboExtractType")
            cboExtractFor.Text = GetSetting(REGAPP, Name, "cboExtractFor")
            cboFirstMonth.Text = GetSetting(REGAPP, Name, "cboFirstMonth")
            cboLastMonth.Text = GetSetting(REGAPP, Name, "cboLastMonth")
            cboMissingValues.Text = GetSetting(REGAPP, Name, "cboMissingValues")
            cboTableName.Text = GetSetting(REGAPP, Name, "cboTableName")
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Sub cboTableName_SelectionChangeCommitted(ByVal sender As Object, ByVal e As System.EventArgs) Handles cboTableName.SelectionChangeCommitted
        frmCalc.TableSelectionCommitted(cboTableName)
    End Sub

    Private Sub cboExtractFor_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cboExtractFor.SelectedIndexChanged
        Static AlreadyWarned As Boolean = False
        pnlRange.Enabled = cboExtractFor.Text.StartsWith("Range")
        If cboExtractFor.Text.StartsWith("Range") Then
            cboFirstMonth.SelectedIndex = 0
            cboLastMonth.SelectedIndex = 0
        ElseIf cboExtractFor.Text.StartsWith("Calendar") Then
            cboFirstMonth.SelectedIndex = 0
            cboLastMonth.SelectedIndex = 11
        ElseIf cboExtractFor.Text.StartsWith("Water") Then 'October previous year to September this year
            cboFirstMonth.SelectedIndex = 9
            cboLastMonth.SelectedIndex = 8
            If Not AlreadyWarned And cboExtractType.SelectedIndex = 0 Then
                WarningMsg(StringFormat("It may be inappropriate to perform a low-flow analysis using the water-year setting because of the natural autocorrelation which occurs during low-flow periods. That is, annual low-flow which occurs in September of one water year may persist into the following October, and the two years' low flows would not be statistically independent, as required by all frequency analyses.\n\nThis message will not be repeated this session."))
                AlreadyWarned = True
            End If
        ElseIf cboExtractFor.Text.StartsWith("Climatic") Then 'April to March
            cboFirstMonth.SelectedIndex = 3
            cboLastMonth.SelectedIndex = 2
        Else
            cboFirstMonth.SelectedIndex = cboExtractFor.SelectedIndex - 3
            cboLastMonth.SelectedIndex = cboExtractFor.SelectedIndex - 3
        End If
    End Sub

    Private Sub btnHelp_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnHelp.Click
        ShowHelp(Me, "8")
    End Sub

    Private Sub TabMain_SelectedIndexChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles TabMain.SelectedIndexChanged
        btnCalc.Text = Choose(TabMain.SelectedIndex + 1, "&Next", "&Finish")
        If TabMain.SelectedIndex = 1 Then 'force calculation
            If Not CalcLowFlow() Then TabMain.SelectedIndex = 0
        End If
    End Sub

    Private Sub btnCalc_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnCalc.Click
        Try
            If TabMain.SelectedIndex = 0 Then
                TabMain.SelectedIndex = 1 'will force recalc
            Else 'send to graph routine
                If MessageBox.Show(StringFormat("{0} records will be added to table {1}.\n\nDo you want to proceed with the operation?", lstAnnualSeries.Count, DataTable.TableName), "Calculate Low Flow Annual Series", MessageBoxButtons.OKCancel, MessageBoxIcon.Information) = Windows.Forms.DialogResult.OK Then
                    DataTable.Close(clsDataTable.enumEndTransaction.Commit)
                    IniFile.SetKey(clsWRDBIniFile.enumSection.Application, clsWRDBIniFile.enumKey.AppLastEditTime, Now.ToString(System.Globalization.CultureInfo.InvariantCulture.DateTimeFormat))
                    MainForm.OpenDataTable(TableName)
                    DialogResult = Windows.Forms.DialogResult.OK
                    SaveSetting(REGAPP, Name, "cboPCodeIn", cboPCodeIn.Text)
                    SaveSetting(REGAPP, Name, "cboCCodeIn", cboCCodeIn.Text)
                    SaveSetting(REGAPP, Name, "cboPCodeOut", cboPCodeOut.Text)
                    SaveSetting(REGAPP, Name, "cboCCodeOut", cboCCodeOut.Text)
                    SaveSetting(REGAPP, Name, "cboNumDays", cboNumDays.Text)
                    SaveSetting(REGAPP, Name, "cboExtractType", cboExtractType.Text)
                    SaveSetting(REGAPP, Name, "cboExtractFor", cboExtractFor.Text)
                    SaveSetting(REGAPP, Name, "cboFirstMonth", cboFirstMonth.Text)
                    SaveSetting(REGAPP, Name, "cboLastMonth", cboLastMonth.Text)
                    SaveSetting(REGAPP, Name, "cboMissingValues", cboMissingValues.Text)
                    SaveSetting(REGAPP, Name, "cboTableName", cboTableName.Text)
                Else
                    DataTable.Close(clsDataTable.enumEndTransaction.Rollback)
                    DialogResult = Windows.Forms.DialogResult.Cancel
                End If
                Close()
            End If
        Catch ex As Exception
            ErrorMsg(, ex)
        End Try
    End Sub

    Private Sub dgDaily_CellValueNeeded(ByVal sender As Object, ByVal e As System.Windows.Forms.DataGridViewCellValueEventArgs) Handles dgDaily.CellValueNeeded
        If lstDataRecord Is Nothing Then Exit Sub
        If e.ColumnIndex = 0 Then
            e.Value = lstDataRecord(e.RowIndex).DateTime
        ElseIf e.ColumnIndex = 1 Then
            e.Value = lstDataRecord(e.RowIndex).Result
        ElseIf e.ColumnIndex = 2 Then
            e.Value = lstMovingAvg(e.RowIndex).Result
        End If
    End Sub

    Private Sub cboPCodeOut_SelectionChangeCommitted(ByVal sender As Object, ByVal e As System.EventArgs) Handles cboPCodeOut.SelectionChangeCommitted
        frmCalc.PCodeSelectionCommitted(cboPCodeOut)
    End Sub

    Private Sub cboCCodeOut_SelectionChangeCommitted(ByVal sender As Object, ByVal e As System.EventArgs) Handles cboCCodeOut.SelectionChangeCommitted
        frmCalc.CCodeSelectionCommitted(cboCCodeOut)
    End Sub
End Class
